﻿//////////////////////////////////////////////////////////////////////////////
//This file is part of My Nes                                               //
//A Nintendo Entertainment System Emulator.                                 //
//                                                                          //
//Copyright © 2009 Ala Hadid (AHD)                                          //
//                                                                          //
//My Nes is free software; you can redistribute it and/or modify            //
//it under the terms of the GNU General Public License as published by      //
//the Free Software Foundation; either version 2 of the License, or         //
//(at your option) any later version.                                       //
//                                                                          //
//My Nes is distributed in the hope that it will be useful,                 //
//but WITHOUT ANY WARRANTY; without even the implied warranty of            //
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             //
//GNU General Public License for more details.                              //
//                                                                          //
//You should have received a copy of the GNU General Public License         //
//along with this program; if not, write to the Free Software               //
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA//
//////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace AHD.MyNes.Nes
{
    using System.Runtime.Serialization;
    using System.Windows.Media.Imaging;

    public class State
    {

        NesEmulator NesEmu;
        public State(NesEmulator Nes)
        { NesEmu = Nes; }

        public byte[] SaveState()
        {
            using (MemoryStream stream = new MemoryStream())
            {
                dataholder dat = new dataholder();
                dat.save(NesEmu);
                DataContractSerializer serializer = new DataContractSerializer(typeof (dataholder));
                serializer.WriteObject(stream, dat);
                //Set capacity
                return GetTrueMemoryBuffer(stream);
            }
            
        }

        public static byte[] GetTrueMemoryBuffer(MemoryStream stream) {
            byte[] filledBuffer = new byte[stream.Length];
            Array.Copy(stream.GetBuffer(), filledBuffer, (int) stream.Length);
            return filledBuffer;
        }

        public void LoadState(byte[] buffer)
        {
            using (MemoryStream stream = new MemoryStream(buffer,0, buffer.Length, false))
            {
                DataContractSerializer serializer = new DataContractSerializer(typeof (dataholder));
                dataholder dat = (dataholder) serializer.ReadObject(stream);
                dat.load(NesEmu);
            }
        }
    }

    public class StateInfo
    {
        public WriteableBitmap screen = new WriteableBitmap(256,240);
        public DateTime date;

        public StateInfo(Stream stream)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(dataholder));
            dataholder dat = (dataholder)serializer.ReadObject(stream);
            date = dat.date;
            Array.Copy(dat.screenshot, screen.Pixels, screen.Pixels.Length);
        }
    }

    [DataContract]
    public class dataholder
    {
        //cpu
        //Registers
        [DataMember]
        public byte a_register;
        
        [DataMember]
        public byte x_index_register;
        [DataMember]
        public byte y_index_register;
        [DataMember]
        public byte sp_register;
        [DataMember]
        public ushort pc_register;
        //Flags
        [DataMember]
        public byte carry_flag;
        [DataMember]
        public byte zero_flag;
        [DataMember]
        public byte interrupt_flag;
        [DataMember]
        public byte decimal_flag;
        [DataMember]
        public byte brk_flag;
        [DataMember]
        public byte overflow_flag;
        [DataMember]
        public byte sign_flag;
        //Ticks
        [DataMember]
        public uint tick_count;
        //Helper variables
        [DataMember]
        public byte currentOpcode;
        [DataMember]
        public ushort previousPC;
        //ppu
        [DataMember]
        public int backgroundAddress; // 0000 or 1000
        [DataMember]
        public int spriteAddress; // 0000 or 1000
        [DataMember]
        public int nameTableAddress; // 2000, 2400, 2800, 2c00
        [DataMember]
        public bool backgroundVisible; // false = invisible
        [DataMember]
        public bool spritesVisible; // false = sprites invisible

        [DataMember]
        public byte scrollV;
        [DataMember]
        public byte scrollH;
        [DataMember]
        public int currentScanline;
        [DataMember]
        public byte[] nameTables;
        [DataMember]
        public byte[] spriteRam;
        [DataMember]
        public int frameCounter;
        [DataMember]
        public uint _frameElapsedCycles;
        [DataMember]
        public bool PalTiming = false;
        [DataMember]
        public bool IsPaused = false;
        [DataMember]
        public MIRRORING mirroring;
        [DataMember]
        public bool trainer_present;
        [DataMember]
        public bool save_ram_present;
        [DataMember]
        public bool is_vram;
        [DataMember]
        public byte mapper;
        [DataMember]
        public byte prg_rom_pages;
        [DataMember]
        public byte chr_rom_pages;
        [DataMember]
        public uint mirroringBase; //For one screen mirroring

        [DataMember]
        public int[] screenshot;

        [DataMember]
        public DateTime date;

        [DataMember]
        public byte[][] scratchRam;
        [DataMember]
        public byte[] saveRam;
        //mappers
        [DataMember]
        public uint[] current_prg_rom_page;
        [DataMember]
        public uint[] current_chr_rom_page;
        //Mapper #1:
        [DataMember]
        public int mapper1_register8000BitPosition;
        [DataMember]
        public int mapper1_registerA000BitPosition;
        [DataMember]
        public int mapper1_registerC000BitPosition;
        [DataMember]
        public int mapper1_registerE000BitPosition;
        [DataMember]
        public int mapper1_register8000Value;
        [DataMember]
        public int mapper1_registerA000Value;
        [DataMember]
        public int mapper1_registerC000Value;
        [DataMember]
        public int mapper1_registerE000Value;
        [DataMember]
        public byte mapper1_mirroringFlag;
        [DataMember]
        public byte mapper1_onePageMirroring;
        [DataMember]
        public byte mapper1_prgSwitchingArea;
        [DataMember]
        public byte mapper1_prgSwitchingSize;
        [DataMember]
        public byte mapper1_vromSwitchingSize;
        [DataMember]
        public int mapper4_commandNumber;
        [DataMember]
        public int mapper4_prgAddressSelect;
        [DataMember]
        public int mapper4_chrAddressSelect;
        [DataMember]
        public byte mapper5_prgBankSize;
        [DataMember]
        public byte mapper5_chrBankSize;
        [DataMember]
        public int mapper5_scanlineSplit;
        [DataMember]
        public bool mapper5_splitIrqEnabled;
        //These are for Mappers 9 and 10
        [DataMember]
        public byte latch1, latch2;
        [DataMember]
        public int latch1data1, latch1data2;
        [DataMember]
        public int latch2data1, latch2data2;
        [DataMember]
        public byte mapper64_commandNumber;
        [DataMember]
        public byte mapper64_prgAddressSelect;
        [DataMember]
        public byte mapper64_chrAddressSelect;
        [DataMember]
        public bool timer_irq_enabled;
        [DataMember]
        public bool timer_reload_next;
        [DataMember]
        public uint timer_irq_count, timer_irq_reload;
        [DataMember]
        public bool timer_zero_pulse;

        [DataMember]
        public int mapper32SwitchingMode = 0;
        [DataMember]
        public ushort timer_irq_counter_16 = 0;
        [DataMember]
        public ushort timer_irq_Latch_16 = 0;
        [DataMember]
        public ushort Mapper18_Timer = 0;
        [DataMember]
        public ushort Mapper18_latch = 0;
        [DataMember]
        public byte mapper18_control = 0;
        [DataMember]
        public int Mapper18_IRQWidth = 0;
        [DataMember]
        public byte Mapper33_Timer, Mapper33_latch = 0;
        [DataMember]
        public int type = 1;
        [DataMember]
        public byte[] x = new byte[22];
        [DataMember]
        public byte Mapper41_CHR_Low = 0;
        [DataMember]
        public byte Mapper41_CHR_High = 0;
        [DataMember]
        public byte Mapper225_reg0 = 0xF;
        [DataMember]
        public byte Mapper225_reg1 = 0xF;
        [DataMember]
        public byte Mapper225_reg2 = 0xF;
        [DataMember]
        public byte Mapper225_reg3 = 0xF;

        public void save(NesEmulator NesEmu)
        {
             
            //nes emulator
            //fix_bgchange=NesEmu.fix_bgchange;
            //fix_spritehit=NesEmu.fix_spritehit;
            //fix_scrolloffset1=NesEmu.fix_scrolloffset1;
            //fix_scrolloffset2=NesEmu.fix_scrolloffset2;
            //fix_scrolloffset3=NesEmu.fix_scrolloffset3;
            scratchRam = NesEmu.scratchRam;
            saveRam = NesEmu.saveRam;
            //cpu
            //Registers
            a_register = NesEmu.my6502.a_register;
            x_index_register = NesEmu.my6502.x_index_register;
            y_index_register = NesEmu.my6502.y_index_register;
            sp_register = NesEmu.my6502.sp_register;
            pc_register = NesEmu.my6502.pc_register;
            //Flags
            carry_flag = NesEmu.my6502.carry_flag;
            zero_flag = NesEmu.my6502.zero_flag;
            interrupt_flag = NesEmu.my6502.interrupt_flag;
            decimal_flag = NesEmu.my6502.decimal_flag;
            brk_flag = NesEmu.my6502.brk_flag;
            overflow_flag = NesEmu.my6502.overflow_flag;
            sign_flag = NesEmu.my6502.sign_flag;
            //Ticks
            tick_count = NesEmu.my6502.tick_count;
            //Helper variables
            currentOpcode = NesEmu.my6502.currentOpcode;
            previousPC = NesEmu.my6502.previousPC;

            //ppu

            backgroundAddress = NesEmu.myPPU.backgroundAddress;
            spriteAddress = NesEmu.myPPU.spriteAddress;
            nameTableAddress = NesEmu.myPPU.nameTableAddress;

            backgroundVisible = NesEmu.myPPU.backgroundVisible;
            spritesVisible = NesEmu.myPPU.spritesVisible;
            scrollV = NesEmu.myPPU.scrollV;
            scrollH = NesEmu.myPPU.scrollH;
            currentScanline = NesEmu.myPPU.currentScanline;
            nameTables = NesEmu.myPPU.nameTables;
            spriteRam = NesEmu.myPPU.spriteRam;
            frameCounter = NesEmu.myPPU.frameCounter;
            //offscreenBuffer=NesEmu.myPPU.offscreenBuffer;


            //apu
            // _bufferSize=NesEmu.myapu._bufferSize;
            // _channelEnabled=NesEmu.myapu._channelEnabled;
            // _dataPosition=NesEmu.myapu._dataPosition;
            // _firstRender =NesEmu.myapu._firstRender ;
            // _frameCounter=NesEmu.myapu._frameCounter;
            // frameCycles=NesEmu.myapu.frameCycles;
            _frameElapsedCycles = NesEmu.myapu._frameElapsedCycles;
            //  _lastPosition=NesEmu.myapu._lastPosition;
            PalTiming = NesEmu.myapu._palTiming;
            //_MasterVolume =NesEmu.myapu._MasterVolume;

            //cart
            //save prg_rom



            //chr_rom=NesEmu.myCartridge.chr_rom;
            mirroring = NesEmu.myCartridge.mirroring;
            trainer_present = NesEmu.myCartridge.trainer_present;
            save_ram_present = NesEmu.myCartridge.save_ram_present;
            is_vram = NesEmu.myCartridge.is_vram;
            mapper = NesEmu.myCartridge.mapper;

            prg_rom_pages = NesEmu.myCartridge.prg_rom_pages;
            chr_rom_pages = NesEmu.myCartridge.chr_rom_pages;
            mirroringBase = NesEmu.myCartridge.mirroringBase; //For one screen mirroring*/
            //mappers
            current_prg_rom_page = NesEmu.myMapper.current_prg_rom_page;
            current_chr_rom_page = NesEmu.myMapper.current_chr_rom_page;
            //Mapper #1:
            mapper1_register8000BitPosition = NesEmu.myMapper.mapper1_register8000BitPosition;
            mapper1_registerA000BitPosition = NesEmu.myMapper.mapper1_registerA000BitPosition;
            mapper1_registerC000BitPosition = NesEmu.myMapper.mapper1_registerC000BitPosition;
            mapper1_registerE000BitPosition = NesEmu.myMapper.mapper1_registerE000BitPosition;
            mapper1_register8000Value = NesEmu.myMapper.mapper1_register8000Value;
            mapper1_registerA000Value = NesEmu.myMapper.mapper1_registerA000Value;
            mapper1_registerC000Value = NesEmu.myMapper.mapper1_registerC000Value;
            mapper1_registerE000Value = NesEmu.myMapper.mapper1_registerE000Value;
            mapper1_mirroringFlag = NesEmu.myMapper.mapper1_mirroringFlag;
            mapper1_onePageMirroring = NesEmu.myMapper.mapper1_onePageMirroring;
            mapper1_prgSwitchingArea = NesEmu.myMapper.mapper1_prgSwitchingArea;
            mapper1_prgSwitchingSize = NesEmu.myMapper.mapper1_prgSwitchingSize;
            mapper1_vromSwitchingSize = NesEmu.myMapper.mapper1_vromSwitchingSize;
            mapper4_commandNumber = NesEmu.myMapper.mapper4_commandNumber;
            mapper4_prgAddressSelect = NesEmu.myMapper.mapper4_prgAddressSelect;
            mapper4_chrAddressSelect = NesEmu.myMapper.mapper4_chrAddressSelect;
            mapper5_prgBankSize = NesEmu.myMapper.mapper5_prgBankSize;
            mapper5_chrBankSize = NesEmu.myMapper.mapper5_chrBankSize;
            mapper5_scanlineSplit = NesEmu.myMapper.mapper5_scanlineSplit;
            mapper5_splitIrqEnabled = NesEmu.myMapper.mapper5_splitIrqEnabled;
            //These are for Mappers 9 and 10
            latch1 = NesEmu.myMapper.latch1;
            latch2 = NesEmu.myMapper.latch2;
            latch1data1 = NesEmu.myMapper.latch1data1;
            latch1data2 = NesEmu.myMapper.latch1data2;
            latch2data1 = NesEmu.myMapper.latch2data1;
            latch2data2 = NesEmu.myMapper.latch2data2;

            mapper64_commandNumber = NesEmu.myMapper.mapper64_commandNumber;
            mapper64_prgAddressSelect = NesEmu.myMapper.mapper64_prgAddressSelect;
            mapper64_chrAddressSelect = NesEmu.myMapper.mapper64_chrAddressSelect;
            timer_irq_enabled = NesEmu.myMapper.timer_irq_enabled;
            timer_reload_next = NesEmu.myMapper.timer_reload_next;
            timer_irq_count = NesEmu.myMapper.timer_irq_count;
            timer_irq_reload = NesEmu.myMapper.timer_irq_reload;

            timer_zero_pulse = NesEmu.myMapper.timer_zero_pulse;

            mapper32SwitchingMode = NesEmu.myMapper.mapper32SwitchingMode;
            timer_irq_counter_16 = NesEmu.myMapper.timer_irq_counter_16;
            timer_irq_Latch_16 = NesEmu.myMapper.timer_irq_Latch_16;
            Mapper18_Timer = NesEmu.myMapper.Mapper18_Timer;
            Mapper18_latch = NesEmu.myMapper.Mapper18_latch;
            mapper18_control = NesEmu.myMapper.mapper18_control;
            Mapper18_IRQWidth = NesEmu.myMapper.Mapper18_IRQWidth;
            Mapper33_Timer = NesEmu.myMapper.Mapper33_Timer;
            Mapper33_latch = NesEmu.myMapper.Mapper33_latch;
            type = NesEmu.myMapper.type;
            x = NesEmu.myMapper.x;
            Mapper41_CHR_Low = NesEmu.myMapper.Mapper41_CHR_Low;
            Mapper41_CHR_High = NesEmu.myMapper.Mapper41_CHR_High;
            Mapper225_reg0 = NesEmu.myMapper.Mapper225_reg0;
            Mapper225_reg1 = NesEmu.myMapper.Mapper225_reg1;
            Mapper225_reg2 = NesEmu.myMapper.Mapper225_reg2;
            Mapper225_reg3 = NesEmu.myMapper.Mapper225_reg3;

            date = DateTime.Now;
            screenshot = NesEmu.Vid.GetContents();
        }

        public void load(NesEmulator NesEmu)
        {
            //nes emulator
            //NesEmu.fix_bgchange = fix_bgchange;
            //NesEmu.fix_spritehit = fix_spritehit;
            //NesEmu.fix_scrolloffset1 = fix_scrolloffset1;
            //NesEmu.fix_scrolloffset2 = fix_scrolloffset2;
            //NesEmu.fix_scrolloffset3 = fix_scrolloffset3;
            NesEmu.scratchRam = scratchRam;
            NesEmu.saveRam = saveRam;
            //cpu
            //Registers
            NesEmu.my6502.a_register = a_register;
            NesEmu.my6502.x_index_register = x_index_register;
            NesEmu.my6502.y_index_register = y_index_register;
            NesEmu.my6502.sp_register = sp_register;
            NesEmu.my6502.pc_register = pc_register;
            //Flags
            NesEmu.my6502.carry_flag = carry_flag;
            NesEmu.my6502.zero_flag = zero_flag;
            NesEmu.my6502.interrupt_flag = interrupt_flag;
            NesEmu.my6502.decimal_flag = decimal_flag;
            NesEmu.my6502.brk_flag = brk_flag;
            NesEmu.my6502.overflow_flag = overflow_flag;
            NesEmu.my6502.sign_flag = sign_flag;
            //Ticks
            NesEmu.my6502.tick_count = tick_count;
            //Helper variables
            NesEmu.my6502.currentOpcode = currentOpcode;
            NesEmu.my6502.previousPC = previousPC;

            //ppu

            NesEmu.myPPU.backgroundAddress = backgroundAddress;
            NesEmu.myPPU.spriteAddress = spriteAddress;

            NesEmu.myPPU.nameTableAddress = nameTableAddress;

            NesEmu.myPPU.backgroundVisible = backgroundVisible;
            NesEmu.myPPU.spritesVisible = spritesVisible;

            NesEmu.myPPU.scrollV = scrollV;
            NesEmu.myPPU.scrollH = scrollH;
            NesEmu.myPPU.currentScanline = currentScanline;
            NesEmu.myPPU.nameTables = nameTables;
            NesEmu.myPPU.spriteRam = spriteRam;
            NesEmu.myPPU.frameCounter = frameCounter;
            NesEmu.myapu._frameElapsedCycles = _frameElapsedCycles;
            // _lastPosition=NesEmu.myapu._lastPosition;
            NesEmu.myapu._palTiming = PalTiming;
            //  _MasterVolume =NesEmu.myapu._MasterVolume;

            //cart
            // NesEmu.myCartridge.prg_rom=prg_rom;
            // NesEmu.myCartridge.chr_rom=chr_rom;
            //load prg_rom

            NesEmu.myCartridge.mirroring = mirroring;
            NesEmu.myCartridge.trainer_present = trainer_present;
            NesEmu.myCartridge.save_ram_present = save_ram_present;
            NesEmu.myCartridge.is_vram = is_vram;
            NesEmu.myCartridge.mapper = mapper;

            NesEmu.myCartridge.prg_rom_pages = prg_rom_pages;
            NesEmu.myCartridge.chr_rom_pages = chr_rom_pages;
            NesEmu.myCartridge.mirroringBase = mirroringBase; //For one screen mirroring
            //mappers
            NesEmu.myMapper.current_prg_rom_page = current_prg_rom_page;
            NesEmu.myMapper.current_chr_rom_page = current_chr_rom_page;
            //Mapper #1:
            NesEmu.myMapper.mapper1_register8000BitPosition = mapper1_register8000BitPosition;
            NesEmu.myMapper.mapper1_registerA000BitPosition = mapper1_registerA000BitPosition;
            NesEmu.myMapper.mapper1_registerC000BitPosition = mapper1_registerC000BitPosition;
            NesEmu.myMapper.mapper1_registerE000BitPosition = mapper1_registerE000BitPosition;
            NesEmu.myMapper.mapper1_register8000Value = mapper1_register8000Value;
            NesEmu.myMapper.mapper1_registerA000Value = mapper1_registerA000Value;
            NesEmu.myMapper.mapper1_registerC000Value = mapper1_registerC000Value;
            NesEmu.myMapper.mapper1_registerE000Value = mapper1_registerE000Value;
            NesEmu.myMapper.mapper1_mirroringFlag = mapper1_mirroringFlag;
            NesEmu.myMapper.mapper1_onePageMirroring = mapper1_onePageMirroring;
            NesEmu.myMapper.mapper1_prgSwitchingArea = mapper1_prgSwitchingArea;
            NesEmu.myMapper.mapper1_prgSwitchingSize = mapper1_prgSwitchingSize;
            NesEmu.myMapper.mapper1_vromSwitchingSize = mapper1_vromSwitchingSize;
            NesEmu.myMapper.mapper4_commandNumber = mapper4_commandNumber;
            NesEmu.myMapper.mapper4_prgAddressSelect = mapper4_prgAddressSelect;
            NesEmu.myMapper.mapper4_chrAddressSelect = mapper4_chrAddressSelect;
            NesEmu.myMapper.mapper5_prgBankSize = mapper5_prgBankSize;
            NesEmu.myMapper.mapper5_chrBankSize = mapper5_chrBankSize;
            NesEmu.myMapper.mapper5_scanlineSplit = mapper5_scanlineSplit;
            NesEmu.myMapper.mapper5_splitIrqEnabled = mapper5_splitIrqEnabled;
            //These are for Mappers 9 and 10
            NesEmu.myMapper.latch1 = latch1;
            NesEmu.myMapper.latch2 = latch2;
            NesEmu.myMapper.latch1data1 = latch1data1;
            NesEmu.myMapper.latch1data2 = latch1data2;
            NesEmu.myMapper.latch2data1 = latch2data1;
            NesEmu.myMapper.latch2data2 = latch2data2;

            NesEmu.myMapper.mapper64_commandNumber = mapper64_commandNumber;
            NesEmu.myMapper.mapper64_prgAddressSelect = mapper64_prgAddressSelect;
            NesEmu.myMapper.mapper64_chrAddressSelect = mapper64_chrAddressSelect;
            NesEmu.myMapper.timer_irq_enabled = timer_irq_enabled;
            NesEmu.myMapper.timer_reload_next = timer_reload_next;
            NesEmu.myMapper.timer_irq_count = timer_irq_count;
            NesEmu.myMapper.timer_irq_reload = timer_irq_reload;

            NesEmu.myMapper.timer_zero_pulse = timer_zero_pulse;

            NesEmu.myMapper.mapper32SwitchingMode = mapper32SwitchingMode;
            NesEmu.myMapper.timer_irq_counter_16 = timer_irq_counter_16;
            NesEmu.myMapper.timer_irq_Latch_16 = timer_irq_Latch_16;
            NesEmu.myMapper.Mapper18_Timer = Mapper18_Timer;
            NesEmu.myMapper.Mapper18_latch = Mapper18_latch;
            NesEmu.myMapper.mapper18_control = mapper18_control;
            NesEmu.myMapper.Mapper18_IRQWidth = Mapper18_IRQWidth;
            NesEmu.myMapper.Mapper33_Timer = Mapper33_Timer;
            NesEmu.myMapper.Mapper33_latch = Mapper33_latch;
            NesEmu.myMapper.type = type;
            NesEmu.myMapper.x = x;
            NesEmu.myMapper.Mapper41_CHR_Low = Mapper41_CHR_Low;
            NesEmu.myMapper.Mapper41_CHR_High = Mapper41_CHR_High;
            NesEmu.myMapper.Mapper225_reg0 = Mapper225_reg0;
            NesEmu.myMapper.Mapper225_reg1 = Mapper225_reg1;
            NesEmu.myMapper.Mapper225_reg2 = Mapper225_reg2;
            NesEmu.myMapper.Mapper225_reg3 = Mapper225_reg3;
        }
    }
}
